package device

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/samber/lo"

	"device-admin/config/mysql"
	"device-admin/config/redis"
	"device-admin/internal/app/grpc/client"
	"device-admin/internal/app/grpc/protoc/advertisingc"
	"device-admin/internal/app/grpc/protoc/sale_adminc"
	"device-admin/internal/app/grpc/protoc/system_admin"
	"device-admin/internal/app/web/service/common"
	"device-admin/internal/dal"
	"device-admin/internal/dao"
	"device-admin/internal/define"
	"device-admin/internal/models"

	"sort"

	db_sharding "192.168.1.75/go-pkg/db-sharding"
	"192.168.1.75/go-pkg/errorx"
	"192.168.1.75/go-pkg/helper"
	"192.168.1.75/go-pkg/logx"
	redisx "github.com/go-redis/redis/v8"
	"github.com/qichengzx/coordtransform"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
)

var errLogger = logger.New(
	log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer（日志输出的目标，前缀和日志包含的内容——译者注）
	logger.Config{
		SlowThreshold:             time.Second,  // 慢 SQL 阈值
		LogLevel:                  logger.Error, // 日志级别
		IgnoreRecordNotFoundError: true,         // 忽略ErrRecordNotFound（记录未找到）错误
		Colorful:                  true,         // 禁用彩色打印
	},
)

type DeviceListType struct {
	models.Device             // 设备信息
	DeviceTypeTitle   string  `json:"device_type_title" gorm:"-"`                          // 设备类型名称
	Online            int     `json:"online" gorm:"-"`                                     // 在线状态
	LocationID        string  `json:"location_id" gorm:"column:location_id"`               // 位置id
	LocationName      string  `json:"location_name" gorm:"column:location_name"`           // 位置名称
	LocationLatitude  string  `json:"location_latitude" gorm:"column:location_latitude"`   // 位置名称
	LocationLongitude string  `json:"location_longitude" gorm:"column:location_longitude"` // 位置名称
	FloorID           string  `json:"floor_id" gorm:"column:floor_id"`                     // 楼层id
	PositionID        string  `json:"position_id" gorm:"column:position_id"`               // 精准位置id
	FloorName         string  `json:"floor_name" gorm:"column:floor_name"`                 // 楼层名称
	MerchantName      string  `json:"merchant_name" gorm:"-"`                              // 商户名称
	Mode              int     `json:"mode" gorm:"-"`                                       // 模式
	ModeName          string  `json:"mode_name" gorm:"-"`                                  // 模式名称
	SubMode           int     `json:"sub_mode" gorm:"-"`                                   // 子模式
	SubModeName       string  `json:"sub_mode_name" gorm:"-"`                              // 子模式名称
	TaskDataUpdatedAt int64   `json:"task_data_updated_at" gorm:"-"`                       // 任务最新的更新时间
	Addr              string  `json:"addr" gorm:"column:addr"`                             // 详细地址
	PositionName      string  `json:"position_name" gorm:"column:position_name"`           // 精准位置名称
	DistKM            float64 `json:"dist_km" gorm:"column:dist_km"`                       // 距离KM
	OpeningHours      string  `json:"opening_hours" gorm:"column:opening_hours;type:string;size:20;comment:营业时间"`
}

// AddDevice 添加设备
// robotID-设备id，robotIDCode-识别码
func AddDevice(robotID string, identificationCode string, deviceType int) error {
	db := mysql.NewDB()
	// 判断设备id或识别码是否存在
	var count int64
	db.Model(&models.Device{}).
		Where("robot_id = ?", robotID).
		Where("identification_code = ?", identificationCode).
		Count(&count)
	if count > 0 {
		return errorx.New("设备已经存在", -1)
	}
	// 新增记录
	rowsAffected := db.Model(&models.Device{}).Create(&models.Device{
		RobotID:            robotID,
		IdentificationCode: identificationCode,
		DeviceType:         deviceType,
	}).RowsAffected
	if rowsAffected <= 0 {
		return errorx.New("新增设备失败", -1)
	}
	return nil
}

// 批量创建设备，根据前缀及序号生成设备id及识别码
func AddDevices(robotIDPrefix string, startIndex, endIndex, indexLen, deviceType int) (int64, error) {
	db := mysql.NewDB()
	if robotIDPrefix == "" {
		return 0, errorx.New("设备前缀不能为空", -1)
	}
	if startIndex <= 0 || endIndex <= 0 || startIndex > endIndex {
		return 0, errorx.New("设备序号有误")
	}
	if indexLen <= 0 {
		return 0, errorx.New("设备序号长度有误", -1)
	}

	var devices []models.Device
	// 批量生成序号及识别码
	for index := startIndex; index <= endIndex; index++ {
		robotID := robotIDPrefix + fmt.Sprintf("%0"+strconv.Itoa(indexLen)+"d", index)
		devices = append(devices, models.Device{
			RobotID:            robotID,
			IdentificationCode: helper.Md5(helper.RandString(128)),
			DeviceType:         deviceType,
		})
	}
	rowsAffected := db.Model(&models.Device{}).
		Select("id", "robot_id", "identification_code", "created_at", "device_type").
		Omit("updated_at").
		Create(&devices).RowsAffected
	if rowsAffected <= 0 {
		return 0, errorx.New("批量创建设备失败", -1)
	}
	return rowsAffected, nil
}

// DeviceDetail 设备详情
func DeviceDetail(merchantID, merchantPath, robotID string) (DeviceListType, error) {
	list, total, _ := DeviceList(
		merchantID,
		merchantPath,
		robotID,
		"",
		"",
		-1,
		1,
		1,
		nil,
		"", "", "", "",
		0,
		0,
		0,
	)
	if total == 0 {
		return DeviceListType{}, errorx.New("设备不存在", -1)
	}
	return list[0], nil
}

func GeoRadius(longitude, latitude *float64) (geoLocation []redisx.GeoLocation, err error) {
	rdb := redis.NewDB()
	key := define.RedisGeo
	var lng, lat float64
	if longitude == nil && latitude == nil {
		lng = 114.058342
		lat = 22.553623
	} else {
		lng = *longitude
		lat = *latitude
	}
	lng, lat = coordtransform.GCJ02toWGS84(lng, lat)
	geoLocation, err = rdb.GeoRadius(context.Background(), key, lng, lat, &redisx.GeoRadiusQuery{
		Radius:    100,   // 范围100km
		Unit:      "km",  // 单位km
		Sort:      "ASC", // 由近到远
		WithCoord: true,  // 返回经纬度坐标
		WithDist:  true,  // 返回距离
	}).Result()
	return
}

// DeviceList 设备列表
// loginUserMerchantID 登录用户的商户id,robotID 机器人编码, deviceType 设备类型, limit 每页条数, page 分页
func DeviceList(loginUserMerchantID, loginUserMerchantPath,
	robotID string, deviceTypes string, merchantIDs string,
	online, page int, limit int, fields []string,
	provinceID, cityID, districtID, locationID string,
	subMode int, longitude, latitude float64,
) (list []DeviceListType, total int64, err error) {

	// rdb0 := redis.NewDB()
	// rdb0.GeoAdd(context.Background(),
	// 	define.RedisGeo,
	// 	&_redis.GeoLocation{
	// 		Name:      "DDRetail042",
	// 		Longitude: longitude + 0.01,
	// 		Latitude:  latitude,
	// 	},
	// )
	// rdb0.Expire(context.Background(), define.RedisGeo, time.Hour)

	var (
		db         = mysql.NewDB()
		deviceList []DeviceListType
	)
	fieldMap := map[string]string{
		"id":                  "device.id",
		"robot_id":            "device.robot_id",
		"online":              "device.latest_active_time",
		"device_type":         "device.device_type",
		"identification_code": "device.identification_code",
		"merchant_id":         "device.merchant_id",
		"location_id":         "device.location_id",
		"floor_name":          "floor.name as floor_name",
		"location_name":       "location.name as location_name",
		"position_id":         "device.position_id",
		"position_name":       "device_position.name as position_name",
		"images":              "device.images",
	}
	// 去除空格
	robotID = strings.TrimSpace(robotID)

	tx := db.Model(&models.Device{})
	if len(fields) == 0 {
		tx = tx.Select(
			"device.id",
			"device.robot_id",
			"device.identification_code",
			"device.merchant_id",
			"device.device_type",
			"device.location_id",
			"device.floor_id",
			"device.position_id",
			"device.latest_active_time",
			"device.total_running_odometer",  // 单位厘米
			"device.total_running_duration",  // 单位ms
			"device.total_charging_duration", // 单位s
			"device.total_task_odometer",     // 任务里程单位厘米
			"device.total_task_duration",     // 总的任务时长，单位毫秒
			"device.total_idle_duration",     // 总空闲时长，单位秒
			"device.service_phone",           // 设备服务电话
			"device.battery",                 // 设备电量
			"location.name as location_name",
			"location.opening_hours as location_opening_hours",
			"location.latitude as location_latitude",
			"location.longitude as location_longitude",
			"location.province_id",
			"location.city_id",
			"location.addr",
			"floor.name as floor_name",
			"device.position_id",
			"device_position.name as position_name",
			"device.images",
		)
	} else {
		selectFields := []string{}
		for _, field := range fields {
			if _, ok := fieldMap[field]; ok {
				selectFields = append(selectFields, fieldMap[field])
			}
		}
		tx = tx.Select(selectFields)
	}

	tx.Joins("LEFT JOIN location ON device.location_id = location.id").
		Joins("LEFT JOIN floor ON device.floor_id = floor.id").
		Joins("LEFT JOIN device_position ON device.position_id = device_position.id").
		Session(&gorm.Session{})

	var geoLocation []redisx.GeoLocation
	if latitude > 0 && longitude > 0 {
		geoLocation, err = GeoRadius(&longitude, &latitude)
		if len(geoLocation) == 0 || err != nil {
			logx.Info(context.Background(), "DeviceList", logx.Any("geoLocation", 0))
			return nil, 0, err
		}
	}
	distMap := make(map[string]float64)
	for _, v := range geoLocation {
		if robotID != "" {
			robotID += ","
		}
		robotID += (v.Name)
		distMap[v.Name] = v.Dist
	}

	// 查询条件
	{
		// 如果不是平台用户，则只能看到自己或下级商户的设备
		if loginUserMerchantID != "1" || merchantIDs != "" {
			merchantPath := strings.Join([]string{loginUserMerchantPath, loginUserMerchantID}, ">")
			merchantIDs = strings.TrimSpace(merchantIDs)
			tx = tx.Where("merchant_path LIKE ?", merchantPath+"%")
			if merchantIDs != "" {
				tx1 := mysql.NewDB()
				merchantIDArr := strings.Split(merchantIDs, ",")
				for index, merchantID := range merchantIDArr {
					// 获取商户路径
					cli, err := client.GetSystemAdminClient()
					if err != nil {
						return []DeviceListType{}, 0, errorx.New("获取商户信息失败", -1)
					}
					rsp, err := cli.MerchantInfo(context.Background(), &system_admin.MerchantInfoReq{
						MerchantId: merchantID,
					})
					// LIKE条件
					if err == nil && rsp != nil {
						if index == 0 {
							tx1 = tx1.Where("merchant_path LIKE ?", rsp.MerchantPath+">"+merchantID+"%")
						} else {
							tx1.Or("merchant_path LIKE ?", rsp.MerchantPath+">"+merchantID+"%")
						}
					}
				}
				// group条件
				tx = tx.Where(tx1)
			}
		}
		// 设备id
		if robotID != "" {
			robotArr := strings.Split(robotID, ",")
			if len(robotArr) > 1 {
				tx = tx.Where("robot_id IN ?", robotArr)
			} else {
				tx = tx.Where("robot_id LIKE ?", "%"+robotID+"%")
			}
		}
		// 区域group条件
		areaGroup := db.Session(&gorm.Session{NewDB: true})
		// 省份
		if provinceID != "" {
			areaGroup = areaGroup.Where("location.province_id IN ?", strings.Split(provinceID, ","))
		}
		// 城市
		if cityID != "" {
			areaGroup = areaGroup.Where("location.city_id IN ?", strings.Split(cityID, ","))
		}
		// 辖区
		if districtID != "" {
			areaGroup = areaGroup.Where("location.district_id IN ?", strings.Split(districtID, ","))
		}
		// 位置
		if locationID != "" {
			areaGroup = areaGroup.Where("device.location_id IN ?", strings.Split(locationID, ","))
		}
		tx = tx.Where(areaGroup)
		// 设备类型
		deviceTypes := strings.TrimSpace(deviceTypes)
		if deviceTypes != "" {
			deviceTypeArr := strings.Split(deviceTypes, ",")
			tx = tx.Where("device_type IN ?", deviceTypeArr)
		}
		// 是否在线
		if online == 1 {
			tx = tx.Where("latest_active_time > ?", time.Now().Unix()-60)
		} else if online == 0 {
			tx = tx.Where("latest_active_time < ?", time.Now().Unix()-60)
		}
		if subMode > 0 {
			robotIDs, err := dao.Terminal{}.GetSubModeRobotIDs(subMode)
			if err != nil {
				return nil, 0, err
			}
			tx = tx.Where("robot_id IN ?", robotIDs)
		}
		tx = tx.Session(&gorm.Session{})
	}

	// 获取总条数
	tx.Count(&total)

	// 获取记录
	helper.Gorm{}.Paginate(tx, page, limit).
		Order("device.robot_id asc").
		Find(&deviceList)

	for index, device := range deviceList {
		deviceList[index].DistKM = distMap[device.RobotID]
	}

	if latitude > 0 && longitude > 0 {
		sort.Slice(deviceList, func(i, j int) bool {
			return deviceList[i].DistKM < deviceList[j].DistKM
		})
	}
	// 基本信息
	for index, device := range deviceList {
		// 在线状态
		if time.Now().Unix()-device.LatestActiveTime < 60 {
			deviceList[index].Online = 1
		}
		// 设备类型名称
		deviceList[index].DeviceTypeTitle = string(define.DeviceType[(device.DeviceType)])
	}
	// 获取商户列表
	merchantNameMap := make(map[string]string)
	systemAdminClient, err := client.GetSystemAdminClient()
	if err != nil {
		logx.Error(context.Background(), "GetSystemAdminClient err", logx.Err(err))
	}
	rsp, err := systemAdminClient.MerchantList(context.Background(), &system_admin.MerchantListReq{MerchantId: loginUserMerchantID})
	if rsp.Code == 0 || err == nil {
		if len(rsp.List) > 0 {
			for _, merchant := range rsp.List {
				merchantNameMap[merchant.MerchantId] = merchant.MerchantName
			}
		}
	}
	// 设备活跃状态信息
	rdb := redis.NewDB()
	if len(fields) == 0 { // 当出现过滤字段时，仅仅是为了获取基础信息，并不需要获取状态信息
		for index, device := range deviceList {
			// 商户名称
			if device.MerchantID == "1" {
				deviceList[index].MerchantName = "平台"
			} else if device.MerchantID != "" {
				deviceList[index].MerchantName = merchantNameMap[device.MerchantID]
			}
			/*总活跃=历史总活跃+今日活跃*/
			// 超时控制
			redisCtx, redisCancel := context.WithTimeout(context.Background(), time.Second*3)
			defer redisCancel()
			// 获取今日活跃信息
			year := strconv.Itoa(int(time.Now().Year()))
			month := strconv.Itoa(int(time.Now().Month()))
			day := strconv.Itoa(int(time.Now().Day()))
			today := strings.Join([]string{year, month, day}, "-")
			activeInfo, err := rdb.HGetAll(redisCtx, define.RedisDeviceActiveDuration+device.RobotID+":"+today).Result()
			if err == nil {
				// 总里程
				if activeInfo["running_odometer"] != "" {
					if runningOdometer, err := strconv.ParseInt(activeInfo["running_odometer"], 10, 64); err == nil {
						deviceList[index].TotalRunningOdometer += runningOdometer
					}
				}
				// 总活跃时长
				if activeInfo["running_duration"] != "" {
					if runningDuration, err := strconv.ParseInt(activeInfo["running_duration"], 10, 64); err == nil {
						// 总里程
						deviceList[index].TotalRunningDuration += runningDuration
					}
				}
				// 总充电时长
				if activeInfo["charging_duration"] != "" {
					if chargingDuration, err := strconv.ParseInt(activeInfo["charging_duration"], 10, 64); err == nil {
						// 总里程
						deviceList[index].TotalChargingDuration += chargingDuration
					}
				}
				// 总的空闲时长
				if activeInfo["idle_duration"] != "" {
					if idleDuration, err := strconv.ParseInt(activeInfo["idle_duration"], 10, 64); err == nil {
						// 总里程
						deviceList[index].TotalIdleDuration += idleDuration
					}
				}
			}
			// 任务里程及任务时长
			createdDate := time.Now().Format("2006-01-02")
			totalTaskOdometerStr, _ := rdb.Get(redisCtx, define.RedisDeviceTaskRecord+device.RobotID+":"+createdDate+":total_task_odometer").Result()
			if totalTaskOdometerStr == "" {
				totalTaskOdometerStr, _ = rdb.HGet(redisCtx, define.RedisDeviceTaskRecord+device.RobotID+":"+createdDate, "total_task_odometer").Result()
			}
			totalTaskOdometer, _ := strconv.ParseInt(totalTaskOdometerStr, 10, 64)
			totalTaskDurationStr, _ := rdb.Get(redisCtx, define.RedisDeviceTaskRecord+device.RobotID+":"+createdDate+":total_task_duration").Result()
			if totalTaskDurationStr == "" {
				totalTaskDurationStr, _ = rdb.HGet(redisCtx, define.RedisDeviceTaskRecord+device.RobotID+":"+createdDate, "total_task_duration").Result()
			}
			totalTaskDuration, _ := strconv.ParseInt(totalTaskDurationStr, 10, 64)
			deviceList[index].TotalTaskOdometer += totalTaskOdometer
			deviceList[index].TotalTaskDuration += totalTaskDuration

			// 获取模式及名称
			if deviceList[index].Online == 1 {
				var (
					mode    int
					subMode int
				)
				func() {
					redisCtx, redisCancel := context.WithTimeout(context.Background(), time.Second*3)
					defer redisCancel()
					modeStr, _ := redis.NewDB().HGet(redisCtx, define.RedisRobotRTInfo+device.RobotID, "mode").Result()
					subModeStr, _ := redis.NewDB().HGet(redisCtx, define.RedisRobotRTInfo+device.RobotID, "sub_mode").Result()
					mode, _ = strconv.Atoi(modeStr)
					subMode, _ = strconv.Atoi(subModeStr)
				}()
				deviceList[index].Mode = mode
				deviceList[index].SubMode = subMode
				deviceList[index].ModeName = define.NavMode[mode]
				deviceList[index].SubModeName = define.NavSubMode[subMode]
			} else {
				deviceList[index].Mode = -1
				deviceList[index].SubMode = -1
				deviceList[index].ModeName = ""
				deviceList[index].SubModeName = ""
			}
			// 设备任务数据最近的更新时间
			func() {
				redisCtx, redisCancel := context.WithTimeout(context.Background(), time.Second*3)
				defer redisCancel()
				taskDataUpdatedAtStr, _ := redis.NewDB().HGet(redisCtx, define.RedisTaskDataUpdatedAt, device.RobotID).Result()
				taskDataUpdatedAt, _ := strconv.ParseInt(taskDataUpdatedAtStr, 10, 64)
				deviceList[index].TaskDataUpdatedAt = taskDataUpdatedAt
			}()
		}
	}

	return deviceList, total, nil
}

type DeviceActiveInfoType struct {
	models.DeviceActiveDuration
	TotalTaskDuration int64 `json:"total_task_duration" gorm:"-"` // 单位ms
}

// ActiveRecords 活跃记录
func ActiveRecords(robotID string, page, limit int) (list []DeviceActiveInfoType, total int64, err error) {
	db := mysql.NewDB()

	if robotID == "" {
		return []DeviceActiveInfoType{}, 0, errorx.New("设备编号不能为空", -1)
	}
	tx := db.Model(&models.DeviceActiveDuration{}).
		Where("robot_id = ?", robotID).
		Session(&gorm.Session{})

	// 获取条数
	tx.Count(&total)
	if total <= 0 {
		return []DeviceActiveInfoType{}, 0, nil
	}
	// 查询记录
	helper.Gorm{}.Paginate(tx, page, limit).
		Order("created_at desc").
		Find(&list)

	// 追加当日的任务时长
	for index, record := range list {
		// 用于对历史数据的补充
		if record.TaskOdometer == -1 || record.TaskDuration == -1 {
			createdDate := time.Unix(record.CreatedAt, 0).Format("2006-01-02")
			activeDate := time.Unix(record.CreatedAt, 0).Format("2006-1-2")
			var taskRecord models.TaskRecord
			// 统计当天的任务时长及任务里程
			db.Model(&models.TaskRecord{}).
				Select(
					"SUM(odometer) as odometer",
					"SUM(time_duration) as time_duration",
				).
				Where("robot_id = ?", robotID).
				Where("created_date = ?", createdDate).
				Where("total_index > ?", 1).
				Take(&taskRecord)
			// 更新活跃记录
			db.Model(&models.DeviceActiveDuration{}).Where(&models.DeviceActiveDuration{
				RobotID: robotID,
				Date:    activeDate,
			}).Updates(&models.DeviceActiveDuration{
				TaskOdometer: taskRecord.Odometer,
				TaskDuration: taskRecord.TimeDuration,
			})
			// 当日总任务时长，后面需前端调整字段（直接使用TaskDuration）
			list[index].TotalTaskDuration = taskRecord.TimeDuration
		} else {
			// 当日总任务时长
			list[index].TotalTaskDuration = record.TaskDuration
			// 异常时间=24小时-出门时长-任务时长-回充时长-充电时长-待机时长
			list[index].FaultDuration = 24*3600 -
				list[index].ToTaskDuration/1000 -
				list[index].TaskDuration/1000 -
				list[index].ToChargingDuration/1000 -
				list[index].ChargingDuration -
				list[index].IdleDuration
		}
	}
	return list, total, nil
}

// AlarmRecords 告警记录
func AlarmRecords(robotID string, page, limit int) (interface{}, int64, error) {
	var (
		list []struct {
			models.AlarmNotifyRecord
			AlarmName string `json:"alarm_name" gorm:"-"`
		}

		count int64
	)

	dbs := db_sharding.GetDB(context.Background(), mysql.NewDB(), models.AlarmNotifyRecordTpl{}, time.Now().Unix()-3600*24*30, time.Now().Unix())

	if robotID == "" {
		return []string{}, 0, errorx.New("设备编号不能为空", -1)
	}

	tx := dbs.QueryDB.Where("robot_id = ?", robotID).Session(&gorm.Session{})

	// 获取条数
	tx.Count(&count)
	if count <= 0 {
		return []string{}, 0, nil
	}

	// 查询记录
	helper.Gorm{}.Paginate(tx, page, limit).
		Select(
			"id",
			"map_name",
			"pos_x",
			"pos_y",
			"pos_direction",
			"alarm_type",
			"created_at",
			"msg_content",
		).
		Order("created_at desc").
		Find(&list)

	// 记录处理
	for index, record := range list {
		if record.ReportedAt == 0 {
			record.ReportedAt = record.CreatedAt
		}
		// 上报时间
		list[index].ReportedAt = record.ReportedAt // 单位秒
		// 报警类型
		list[index].AlarmName = define.AlarmType[record.AlarmType].Name + fmt.Sprintf("(0x%02x)", record.AlarmType)
	}

	return list, count, nil
}

// TaskRecords 任务执行记录
func TaskRecords(robotID string, page, limit int) (interface{}, int64, error) {
	var (
		list []struct {
			models.TaskRecord
			TaskTypeName string `json:"task_type_name" gorm:"-"`
			WorkTypeName string `json:"work_type_name" gorm:"-"`
		}
		count int64
	)
	db := mysql.NewDB().Model(&models.TaskRecord{})

	if robotID == "" {
		return []string{}, 0, errorx.New("设备编号不能为空", -1)
	}

	tx := db.Where("robot_id = ?", robotID).Session(&gorm.Session{})

	// 获取条数
	tx.Count(&count)
	if count <= 0 {
		return []string{}, 0, nil
	}

	// 查询记录
	helper.Gorm{}.Paginate(tx, page, limit).
		Select(
			"id",
			"task_id",
			"robot_id",
			"task_name",
			"task_type",
			"begin_time",
			"end_time",
			"odometer",
			"map_name",
			"current_index",
			"total_index",
			"time_duration",
			"created_at",
			"user",
		).
		Order("begin_time desc").
		Find(&list)

	// 处理记录
	for index, record := range list {
		list[index].BeginTime = list[index].BeginTime / 1000
		list[index].EndTime = list[index].EndTime / 1000
		list[index].TaskTypeName = define.TaskType[record.TaskType]
		taskName := list[index].TaskName
		totalIndex := list[index].TotalIndex
		if strings.Contains(taskName, "运营") || totalIndex > 1 {
			list[index].WorkType = 1
			list[index].WorkTypeName = define.WorkType[1]
		} else if strings.Contains(strings.ToLower(taskName), "charging") {
			list[index].WorkType = 2
			list[index].WorkTypeName = define.WorkType[2]
		} else {
			list[index].WorkType = 3
			list[index].WorkTypeName = define.WorkType[3]
		}
	}
	return list, count, nil
}

// SetLocationAndSync 设置位置并同步
func SetLocationAndSync(
	ctx context.Context,
	loginUserID, loginUserMerchantPath,
	robotID,
	locationID, floorID, positionID string,
) error {
	// 设置位置
	err := SetLocation(ctx, loginUserID, loginUserMerchantPath, robotID, locationID, floorID, positionID)
	if err != nil {
		return err
	}
	// 同步机器信息
	err = SyncRobotInfo(context.Background(), robotID)
	if err != nil {
		return err
	}
	// 返回结果
	return nil
}

// SetLocation 设置区域
// 平台可以操作全部，其它只能操作自己下的
// loginUserID 当前操作者的用户id，deviceID 设备id，locationID 位置id
func SetLocation(ctx context.Context, loginUserID, loginUserMerchantPath, robotID, locationID, floorID, positionID string) error {
	db := mysql.NewDB()
	if loginUserMerchantPath != ">1" {
		// 判断是否具有操作权限
		if !(dao.Device{}.HasPermissions(ctx, dal.Q, robotID, loginUserMerchantPath)) {
			return errorx.New("无权限", -1)
		}
	}
	// 判断位置是否存在
	var count int64
	db.Model(&models.Location{}).Where(models.Location{
		ID: locationID,
	}).Count(&count)
	if count <= 0 {
		return errorx.New("位置不存在", -1)
	}
	// 判断楼层是否存在
	if floorID != "" {
		var count int64
		db.Model(&models.Floor{}).Where("id = ?", floorID).Count(&count)
		if count <= 0 {
			return errorx.New("楼层不存在", -1)
		}
	}
	// 查询机器原xian的位置信息
	var robotInfo models.Device
	db.Model(&models.Device{}).
		Where(models.Device{
			RobotID: robotID,
		}).
		Select("robot_id,location_id,floor_id,position_id").
		Take(&robotInfo)
	// 旧的位置数据
	oldLocationID := robotInfo.LocationID
	oldFloorID := robotInfo.FloorID
	oldPositionID := robotInfo.PositionID
	// 设置区域
	rowsAffected := db.Model(&models.Device{}).
		Where(models.Device{
			RobotID: robotID,
		}).
		Updates(map[string]interface{}{
			"location_id": locationID,
			"floor_id":    floorID,
			"position_id": positionID,
		}).RowsAffected
	if rowsAffected <= 0 {
		return errorx.New("设置位置失败", -1)
	}
	// 新的位置数据
	newLocationID := locationID
	newFloorID := floorID
	newPositionID := positionID
	// 保存位置变更记录
	{
		userName, _ := common.GetUserNameByID(loginUserID)
		historyDataMap := make(map[string]string)
		currentDataMap := make(map[string]string)
		locationNameMap := dao.Location{}.LocationNameMap(context.Background(), []string{oldLocationID, newLocationID})       // 位置名称map
		floorNameMap := dao.Floor{}.FloorNameMap(context.Background(), []string{oldFloorID, newFloorID})                      // 楼层名称map
		positionNameMap := dao.DevicePosition{}.PositionNameMap(context.Background(), []string{oldPositionID, newPositionID}) // 位置名称map
		historyDataMap["location"] = locationNameMap[oldLocationID]
		historyDataMap["floor"] = floorNameMap[oldFloorID]
		historyDataMap["position"] = positionNameMap[oldPositionID]
		currentDataMap["location"] = locationNameMap[newLocationID]
		currentDataMap["floor"] = floorNameMap[newFloorID]
		currentDataMap["position"] = positionNameMap[newPositionID]
		historyDataMapJson, _ := json.Marshal(historyDataMap)
		currentDataMapJson, _ := json.Marshal(currentDataMap)
		// 如果同个位置内，变更则只能插入一条记录
		// 否则能插入两条记录，分别关联到两个商场
		// 设备操作记录-oldLocation
		if oldLocationID != newLocationID || oldFloorID != newFloorID || oldPositionID != newPositionID {
			dao.OperationLog{}.Add(
				context.Background(),
				dal.Q,
				[]*models.OperationLog{
					{
						OperationType: define.OperationDeviceLocatioinChange,
						ObjID:         robotID,
						AssociationID: oldLocationID,
						HistoryData:   string(historyDataMapJson),
						CurrentData:   string(currentDataMapJson),
						UserName:      userName,
					},
				},
				1,
				dao.Device{}.Exists,
				dao.Location{}.Exists,
			)
			// 设备操作记录-newLocation
			if newLocationID != oldLocationID && newLocationID != "" {
				dao.OperationLog{}.Add(
					context.Background(),
					dal.Q,
					[]*models.OperationLog{
						{
							OperationType: define.OperationDeviceLocatioinChange,
							ObjID:         robotID,
							AssociationID: newLocationID,
							HistoryData:   string(historyDataMapJson),
							CurrentData:   string(currentDataMapJson),
							UserName:      userName,
						},
					},
					1,
					dao.Device{}.Exists,
					dao.Location{}.Exists,
				)
			}
		}
	}
	// 更新设备缓存
	LoadDeviceToRedis(robotID)
	return nil
}

// SetServicePhone 设置服务电话
func SetServicePhone(ctx context.Context, loginUserMerchantPath string, robotID string, servicePhone string) error {
	if robotID == "" {
		return errorx.New("设备id不能为空", -1)
	}
	if servicePhone == "" {
		return errorx.New("服务电话不能为空", -1)
	}
	servicePhoneFilter := ""
	for _, num := range servicePhone {
		if (num >= 48 && num <= 57) || num == '-' {
			servicePhoneFilter += string(num)
		}
	}
	servicePhone = servicePhoneFilter

	db := mysql.NewDB()
	// 非平台商户，只能设置自己的设备
	if loginUserMerchantPath != ">1" {
		// 判断是否具有操作权限
		if !(dao.Device{}.HasPermissions(ctx, dal.Q, robotID, loginUserMerchantPath)) {
			return errorx.New("无权限", -1)
		}
	}
	// 设置区域
	rowsAffected := db.Model(&models.Device{}).
		Where(models.Device{
			RobotID: robotID,
		}).
		Updates(&models.Device{
			ServicePhone: servicePhone,
		}).RowsAffected
	if rowsAffected <= 0 {
		return errorx.New("设置服务电话失败", -1)
	}
	// 更新设备缓存
	LoadDeviceToRedis(robotID)
	return nil
}

// SetImages 设置图片
func SetImages(ctx context.Context, loginUserMerchantPath string, robotID string, images []string) error {
	if robotID == "" {
		return errorx.New("设备id不能为空", -1)
	}

	//db := mysql.NewDB()
	// 非平台商户，只能设置自己的设备
	if loginUserMerchantPath != ">1" {
		// 判断是否具有操作权限
		if !(dao.Device{}.HasPermissions(ctx, dal.Q, robotID, loginUserMerchantPath)) {
			return errorx.New("无权限", -1)
		}
	}

	deviceDetail, _ := dao.Device{}.Detail(ctx, dal.Q, robotID)
	if deviceDetail == nil {
		return errorx.New("设备不存在", -1)
	}

	SystemAdminClient, clientErr := client.GetSystemAdminClient()
	if clientErr != nil {
		logx.Error(ctx, "获取系统管理服务客户端失败", logx.Any("err", clientErr))
		return errorx.New("获取系统管理服务客户端失败", -1)
	}

	//这里把图片从临时目录移动到永久保留目录
	var saveImages []string
	for _, img := range images {
		if img != "" {
			exists := lo.Contains(deviceDetail.Images, img)
			if !exists {
				var req system_admin.ConfirmFileReq
				req.FileUrl = img
				res, err1 := SystemAdminClient.ConfirmFile(ctx, &req)
				if err1 != nil || res.Code != 0 {
					logx.Error(ctx, "save goodsInfo confirm file error", logx.Any("res", res), logx.Any("err", err1))
					return errorx.New("保存图片失败", -1)
				}
				saveImages = append(saveImages, res.FileUrl)
			} else {
				//如果和已保存的图片一致，则继续使用原图片
				saveImages = append(saveImages, img)
			}
		}
	}

	// 设置
	updateDevice := &models.Device{
		Images: saveImages,
	}

	// 使用 DAL 层方法更新 images 字段
	result, err := dal.Q.Device.WithContext(ctx).
		Where(dal.Device.RobotID.Eq(robotID)).
		Select(dal.Device.Images).
		Updates(updateDevice)
	if err != nil || result.Error != nil || result.RowsAffected <= 0 {
		return errorx.New("设置设备图片失败", -1)
	}
	// 更新设备缓存
	LoadDeviceToRedis(robotID)
	return nil
}

// ResetMerchant 重置设备所属商户
func ResetMerchant(ctx context.Context, loginUserID, loginUserMerchantID string, loginUserMerchantPath string, robotID string) error {
	// 重置位置
	err := SetLocation(ctx, loginUserID, loginUserMerchantPath, robotID, "", "", "")
	if err != nil {
		return err
	}
	// 重置服务电话
	dao.Device{}.ResetServicePhone(ctx, dal.Q, robotID)
	// 设置商户
	err = SetMerchant(ctx, loginUserID, loginUserMerchantID, loginUserMerchantPath, robotID, loginUserMerchantID)
	if err != nil {
		return err
	}
	// 同步机器信息
	err = SyncRobotInfo(ctx, robotID)
	if err != nil {
		return err
	}
	// 重置机器商品
	{
		saleAdminCli, err := client.GetSaleAdminClient()
		if err != nil {
			logx.Error(context.Background(), "access sale admin grpc error", logx.Err(err))
		} else {
			goodsResetReq := &sale_adminc.RobotGoodsResetReq{
				RobotId: robotID,
			}
			goodsResetRsp, goodsResetErr := saleAdminCli.RobotGoodsReset(ctx, goodsResetReq)
			if goodsResetErr != nil {
				logx.Error(context.Background(), "reset robot goods error",
					logx.Any("robot goods reset error", goodsResetErr),
					logx.Any("goodsResetRsp", goodsResetRsp))
			}
		}
	}
	// 重置机器广告
	{
		advertisingCli, err := client.GetAdvertisingClient()
		if err != nil {
			logx.Error(context.Background(), "access advertising grpc error", logx.Err(err))
		} else {
			advertisingResetReq := &advertisingc.RobotAdvertisingResetReq{
				RobotId: robotID,
			}
			advertisingResetRsp, advertisingResetErr := advertisingCli.RobotAdvertisingReset(ctx, advertisingResetReq)
			if advertisingResetErr != nil {
				logx.Error(context.Background(), "reset robot advertising error",
					logx.Any("robot advertising reset error", advertisingResetErr),
					logx.Any("advertisingResetRsp", advertisingResetRsp))
			}
		}
	}
	// 返回结果
	return nil
}

// SetMerchantAndSync 设置商户并同步
func SetMerchantAndSync(ctx context.Context, loginUserID, loginUserMerchantID string, loginUserMerchantPath string, robotID string, merchantID string) error {
	// 设置商户
	err := SetMerchant(ctx, loginUserID, loginUserMerchantID, loginUserMerchantPath, robotID, merchantID)
	if err != nil {
		return err
	}
	// 同步机器信息
	err = SyncRobotInfo(ctx, robotID)
	if err != nil {
		return err
	}
	// 返回结果
	return nil
}

// SetMerchant 设置商户
// 平台可以操作全部，其它只能操作自己下的
// loginUserID 当前操作者的用户id，deviceID 设备id，merchantID 商户id
func SetMerchant(ctx context.Context, loginUserID, loginUserMerchantID string, loginUserMerchantPath string, robotID string, merchantID string) error {
	// 判断设备是否存在
	deviceDetail, _ := dao.Device{}.Detail(ctx, dal.Q, robotID)
	if deviceDetail == nil {
		return errorx.New("设备不存在", -1)
	}

	// 非平台商户，只能设置自己的设备
	if loginUserMerchantID != "1" {
		if !(dao.Device{}.HasPermissions(ctx, dal.Q, robotID, loginUserMerchantPath)) {
			return errorx.New("无权限", -1)
		}
	}

	merchantPath := ""
	// 获取商户信息
	{
		cli, err := client.GetSystemAdminClient()
		if err != nil {
			return errorx.New("商户信息获取失败", -1)
		}
		merchantInfo, err := cli.MerchantInfo(ctx, &system_admin.MerchantInfoReq{
			MerchantId: merchantID,
		})
		if err == nil && merchantInfo != nil {
			merchantPath = merchantInfo.MerchantPath
		} else {
			return errorx.New("商户信息获取失败", -1)
		}
	}

	// 设置商户
	{
		q := dal.Q
		q.Transaction(func(tx *dal.Query) error {
			{
				_, err := dao.Device{}.SetMerchant(
					ctx,
					tx,
					robotID,
					merchantID,
					strings.Join([]string{merchantPath, merchantID}, ">"), // 机器的商户路径
				)
				if err != nil {
					return errorx.New("设置所属商户失败", -1)
				}
			}
			// 如果设备所属商户发生变更，则移除设备的位置坐标[用于小程序]
			if deviceDetail.MerchantID != merchantID {
				rdb := redis.NewDB()
				_, err := rdb.ZRem(ctx, define.RedisGeo+deviceDetail.MerchantID, robotID).Result()
				if err != nil {
					return err
				}
			}
			return nil
		})
	}

	// 保存商户变更记录
	{
		_, historyMerchantName, _ := common.GetMerchantInfo(ctx, deviceDetail.MerchantID) // 获取历史位置名称
		_, currentMerchantName, _ := common.GetMerchantInfo(ctx, merchantID)              // 获取最新位置名称
		userName, _ := common.GetUserNameByID(loginUserID)                                // 获取用户名称
		historyDataMap := make(map[string]string)
		currentDataMap := make(map[string]string)
		if historyMerchantName == "" {
			historyMerchantName = "平台"
		}
		if currentMerchantName == "" {
			currentMerchantName = "平台"
		}
		historyDataMap["merchant"] = historyMerchantName
		currentDataMap["merchant"] = currentMerchantName
		historyDataMapJson, _ := json.Marshal(historyDataMap)
		currentDataMapJson, _ := json.Marshal(currentDataMap)
		dao.OperationLog{}.Add(
			ctx, dal.Q,
			[]*models.OperationLog{
				{
					OperationType: define.OperationDeviceLocatioinChange,
					ObjID:         robotID,
					AssociationID: merchantID,
					HistoryData:   string(historyDataMapJson),
					CurrentData:   string(currentDataMapJson),
					UserName:      userName,
				},
			},
			1,
			nil, nil,
		)
	}
	return nil
}

// SyncRobotInfo 同步机器信息
func SyncRobotInfo(ctx context.Context, robotID string) error {
	// 通知零售后台同步
	cli, err := client.GetSaleAdminClient()
	if err != nil {
		logx.Error(ctx, "sale-admin rpc failed", logx.String("robot_id", robotID))
		return errorx.New("同步零售后台失败", -1)
	} else {
		deviceDetail, err := dao.Device{}.Detail(ctx, dal.Q, robotID)
		if err != nil {
			return err
		}
		rsp, _ := cli.SaveRobotInfo(ctx, &sale_adminc.RobotInfoReq{
			RobotId:            robotID,
			IdentificationCode: deviceDetail.IdentificationCode,
			MerchantId:         deviceDetail.MerchantID,
			LocationId:         deviceDetail.LocationID,
			ProvinceId:         deviceDetail.ProvinceID,
			CityId:             deviceDetail.CityID,
			DistrictId:         deviceDetail.DistrictID,
			DeviceType:         int32(deviceDetail.DeviceType),
		})
		if rsp == nil || rsp.Code != 0 {
			return errorx.New("同步零售后台失败", -1)
		}
	}
	return nil
}

// SyncActiveDuration 同步设备活跃记录
func SyncActiveDuration(records []models.DeviceActiveDuration) {
	// 批量插入到数据库
	for _, record := range records {
		// 更新或创建记录
		rowsAffected := mysql.NewDB().
			Model(&models.DeviceActiveDuration{}).
			Where(models.DeviceActiveDuration{
				RobotID: record.RobotID,
				Date:    record.Date,
			}).
			Assign(record).
			FirstOrCreate(&record).
			RowsAffected
		if rowsAffected > 0 {
			// 更新设备活跃时间和运行里程到缓存
			timeoutCtx, cancel := context.WithTimeout(context.Background(), time.Second*3)
			rdb := redis.NewDB()
			// 保存为hash
			_, err := rdb.HSet(timeoutCtx,
				define.RedisDeviceActiveDuration+record.RobotID+":"+record.Date,
				"charging_duration", record.ChargingDuration, // 充电时长
				"running_duration", record.RunningDuration, // 运行时长
				"running_odometer", record.RunningOdometer, // 运行里程
				"idle_duration", record.IdleDuration, // 待机时长
			).Result()
			// 设置过期时间
			rdb.Expire(timeoutCtx, define.RedisDeviceActiveDuration+record.RobotID+":"+record.Date, time.Hour*24)
			if err != nil {
				logx.Error(context.Background(), "cache active_duration info error", logx.Err(err))
			}
			cancel()
		}
	}
}

// LoadDeviceToRedis 加载设备列表到redis缓存
func LoadDeviceToRedis(robotID string) {
	devices, _ := GetDeviceWithLocation(robotID, "", "", "", "", "", 0, 0, "", "", 0)
	if len(devices) > 0 {
		var values []interface{}
		for _, device := range devices {
			dataBytes, _ := json.Marshal(device)
			values = append(values, device.RobotID, string(dataBytes))
		}

		ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
		defer cancel()
		_, err := redis.NewDB().HMSet(ctx, define.RedisDeviceList, values...).Result()
		if err != nil {
			logx.Error(context.Background(), "LoadDeviceToRedis", logx.Err(err))
		}
	}
}

// LoadDeviceToRedis 加载设备列表到redis缓存
func LoadAllDeviceToRedis() {
	// LoadDeviceToRedis("")
}

type DeviceWithLocationType struct {
	RobotID            string `json:"robot_id" gorm:"column:robot_id"`
	IdentificationCode string `json:"identification_code" gorm:"column:identification_code"`
	MerchantID         string `json:"merchant_id" gorm:"column:merchant_id"`
	MerchantPath       string `json:"merchant_path" gorm:"column:merchant_path"`
	LocationID         string `json:"location_id" gorm:"column:location_id"`
	FloorID            string `json:"floor_id" gorm:"column:floor_id"`
	FloorName          string `json:"floor_name" gorm:"-"`
	ProvinceID         string `json:"province_id" gorm:"column:province_id"`
	CityID             string `json:"city_id" gorm:"column:city_id"`
	DistrictID         string `json:"district_id" gorm:"column:district_id"`
	DeviceType         int    `json:"device_type" gorm:"column:device_type"`
	ServicePhone       string `json:"service_phone" gorm:"column:service_phone"`
	CurrentMapName     string `json:"current_map_name" gorm:"column:current_map_name"`
	LatestActiveTime   int64  `json:"latest_active_time" gorm:"column:latest_active_time"`
	Battery            int8   `json:"battery" gorm:"column:battery"`
	SaleStatus         int    `json:"sale_status" gorm:"column:sale_status"`
	OpeningHours       string `json:"opening_hours" gorm:"column:opening_hours"`
	PositionID         string `json:"position_id" gorm:"column:position_id"`
	Addr               string `json:"addr" gorm:"column:addr"`
}

// GetDeviceWithLocation 获取设备及位置信息
func GetDeviceWithLocation(
	robotID,
	identificationCode,
	merchantID,
	provinceID,
	cityID,
	districtID string,
	page, limit int64,
	locationID, floorID string,
	saleStatus int, // 售卖状态
) (
	deviceWithLocation []DeviceWithLocationType,
	total int64,
) {
	tx := mysql.NewDB().Session(&gorm.Session{Logger: errLogger}).Model(&models.Device{}).
		Select(
			"device.robot_id",            // 设备id
			"device.identification_code", // 设备唯一识别码
			"device.latest_active_time",  // 最近活跃时间
			"device.merchant_id",         // 设备所属商户
			"device.merchant_path",       // 商户路径
			"device.location_id",         // 所属商场id
			"device.floor_id",            // 楼层id
			"device.service_phone",       // 设备的服务电话
			"device.current_map_name",    // 当前使用的地图
			"device.device_type",         // 设备类型
			"device.battery",             // 设备电量
			"device.position_id",         // 设置精准位置
			"location.province_id",       // 所在省份id
			"location.city_id",           // 所在城市id
			"location.district_id",       // 所在区或县id
			"location.opening_hours",     // 营业时间
			"location.addr",              // 位置的详细地址
		).
		Joins("LEFT JOIN location ON location.id = device.location_id")
	if robotID != "" {
		tx.Where("device.robot_id = ?", robotID)
	}
	// 售卖状态
	if saleStatus > 0 {
		robotIDs, err := dao.Terminal{}.GetSaleStatusRobotIDs(1)
		if err != nil {
			return []DeviceWithLocationType{}, 0
		}
		if saleStatus == 1 {
			tx.Where("device.robot_id IN ?", robotIDs)
		} else {
			tx.Where("device.robot_id NOT IN ?", robotIDs)
		}
	}
	if identificationCode != "" {
		tx.Where("device.identification_code = ?", identificationCode)
	}
	if merchantID != "" {
		cli, err := client.GetSystemAdminClient()
		if err != nil {
			return deviceWithLocation, total
		}
		rsp, err := cli.MerchantInfo(context.Background(), &system_admin.MerchantInfoReq{
			MerchantId: merchantID,
		})
		if err != nil || rsp == nil {
			return deviceWithLocation, total
		}
		merchantPath := rsp.MerchantPath + ">" + merchantID
		tx.Where("device.merchant_path LIKE ?", merchantPath+"%")
	}
	if provinceID != "" {
		tx.Where("location.province_id IN ?", strings.Split(provinceID, ","))
	}
	if cityID != "" {
		tx.Where("location.city_id IN ?", strings.Split(cityID, ","))
	}
	if districtID != "" {
		tx.Where("location.district_id IN ?", strings.Split(districtID, ","))
	}
	if locationID != "" {
		tx.Where("device.location_id IN ?", strings.Split(locationID, ","))
	}
	if floorID != "" {
		tx.Where("device.floor_id IN ?", strings.Split(floorID, ","))
	}
	tx.Count(&total)
	tx = helper.Gorm{}.Paginate(tx, int(page), int(limit)).Order("device.robot_id")
	tx.Find(&deviceWithLocation)
	// 获取售卖状态
	var (
		robotIDs []string
		floorIDs []string
	)
	for index, record := range deviceWithLocation {
		robotIDs = append(robotIDs, record.RobotID)
		floorIDs = append(floorIDs, record.FloorID)
		deviceWithLocation[index].SaleStatus = 2 // 默认停售状态
	}
	// 获取售卖状态
	{
		var cacheStatus map[string]map[string]int
		cacheStatus, _ = dao.Terminal{}.GetCacheStatus(robotIDs)
		if cacheStatus != nil {
			for index, record := range deviceWithLocation {
				if cacheStatus[record.RobotID] != nil {
					// 售卖状态，1为售卖中，2为停止售卖
					// 当sale_status为0时，若97在线则为售卖中，否则为停止售卖
					status := cacheStatus[record.RobotID]["sale_status"]
					if status == 0 {
						status = 2
					}
					deviceWithLocation[index].SaleStatus = status
				}
			}
		}
	}
	// 楼层名称
	{
		floorMap := dao.Floor{}.FloorNameMap(context.Background(), floorIDs)
		if floorMap != nil {
			for index, record := range deviceWithLocation {
				deviceWithLocation[index].FloorName = floorMap[record.FloorID]
			}
		}
	}
	return deviceWithLocation, total
}

// IsLocationUsed 检查位置是否被设备使用
func IsLocationUsed(locationID string) (bool, error) {
	if locationID == "" {
		return false, errorx.New("位置ID不能为空", -1)
	}

	var count int64
	err := mysql.NewDB().Model(&models.Device{}).
		Where("location_id = ?", locationID).
		Count(&count).Error

	if err != nil {
		return false, err
	}

	return count > 0, nil
}

func DeviceListByLocationUsed(locationIDs []string) ([]models.Device, error) {
	if len(locationIDs) == 0 {
		return []models.Device{}, nil
	}

	// 先对输入参数去重，避免重复查询
	locationIDs = lo.Uniq(locationIDs)

	const batchSize = 1000 // MySQL IN 查询建议不超过1000个参数（经验值）
	var allDevices []models.Device

	batches := lo.Chunk(locationIDs, batchSize)

	for _, batch := range batches {
		var devices []models.Device
		err := mysql.NewDB().Model(&models.Device{}).
			Where("location_id IN ?", batch).
			Find(&devices).Error

		if err != nil {
			return nil, err
		}

		allDevices = append(allDevices, devices...)
	}

	return allDevices, nil
}
